<?php

    /*********************************************************************************
     * LeafPHP router路由器类 www.leafphp.com
     *-------------------------------------------------------------------------------
     * 路由器类，负责解析当前url，返回应该运行的app或group信息
     * 告知app类运行的app信息，然后app去执行
     * 主要开放两个方法：第一个初始（应用或分组，通过传参的方式）化方法，第二个是获取入口方法
     * note：路由器可以单独提取，用于其它项目，经过该路由器，会生成控制器和方法常量APP_CONTROLLER和APP_METHOD
     * 提取之前，可设置一个配置分组（从配置config.php中提取）
     *-------------------------------------------------------------------------------
     * $Author:hongshuaidong
     * $Dtime:2013-11-27
     ***********************************************************************************/

    if (!defined('__IS_LEAF__')) exit('Access Denied!');

    class router {

        static private $runType;        //运行模式：app、group
        static private $params;         //当前的query参数
        static private $urlMode;        //url模式，1代表普通模式，2代表pathinfo模式，3代表rewrite模式
        static private $rewriteSp;      //url重写模式分隔符
        static private $urlSp;          //url的pathinfo模式，或rewrite模式的间隔符
        static private $urlSuffix;          //url的rewrite模式的，url后缀
        //默认App参数
        static private $defaultAppParams = array();


        /**
         * 初始化路由器
         * @access public
         */
        static public function routeInit($type='app'){
            //定义当前url地址常量
            define('APP_URL', 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
            //定义入口文件常量
            define('APP_ROOT_FILE', array_pop( explode('/', $_SERVER['SCRIPT_FILENAME']) ));
            //定义常量，防止重复的调用的函数读取配置，影响效率
            define('__C__', C('url_controler_name'));
            define('__M__', C('url_action_name'));
            self::$defaultAppParams = array(
                                                __C__=>'index',    //默认控制器
                                                __M__=>'index'     //默认行为
                                            );
            //获取运行类型：app或group
            self::$runType   = $type;
            self::$urlMode   = C('url_mode');
            self::$urlSp     = C('url_separator');
            self::$urlSuffix = C('url_rewrite_suffix');
            //获取当前控制器和方法
            self::setParams();

        }

        /**
         * 从url中提取当前需要运行的控制器和方法，并设置GET参数
         * @access private
         */
        static private function setParams(){

            $params = array();
            //获取当前url
            switch (self::$urlMode) {
                //如果url模式为普通模式
                case 1:
                    $params = $_GET; 
                    break;
                //如果为rewrite模式
                case 3:
                    //获取参数数组
                    $params = self::parseRewriteUrl();
                    break;
                case 2:
                    //如果开发者在pathinfo模式下使用的普通url，主要是判断$_SERVER['PATH_INFO']是否有pathinfo模式的内容以做对应的处理
                    //rewrite模式，兼容普通url的话，不能这样处理
                    $params = !empty($_SERVER['PATH_INFO']) ? self::parsePathinfoUrl() : self::parseNormalUrl();
                    break;
            }
            //合并用户定义参数，和系统默认参数，此情况用于，当没有定义参数的情况
            $params = array_merge(self::$defaultAppParams, $params);
            self::$params = $params;
            //重写赋予$_GET参数和值，并去其中关于控制器和方法的参数信息
            $_GET = $params;
            unset($_GET[__C__]);
            unset($_GET[__M__]);
            //合并实际控制器和方法的参数和默认url控制器和方法的参数
            define('APP_CONTROLLER', $params[__C__]);
            define('APP_METHOD', $params[__M__]);

        }

        /**
         * 解析pathinfo模式的url为参数数组列表，此模式兼容普通url模式
         */
        static private function parsePathinfoUrl(){

            //解析url
            $params = parse_url(APP_URL);
            //如果解析当前url，获取的参数中，有query信息，则说明当前url使用了普通的url写法。直接返回$_GET即可。
            if( isset($params['query']) ){
                return $_GET;
            }
            //获取pathinfo部分的字符串，pathinfo模式的根本
            $pathinfo = !empty($_SERVER['PATH_INFO']) ? $_SERVER['PATH_INFO'] : $_SERVER['QUERY_STRING'];
            //兼容普通模式的处理，获取pathinfo类型url或普通url并处理，最终得到诸如'index/index/ss/dd/qq/pp/c/sdd'这种格式的url参数
            //处理url，使得可以支持pathinfo和普通url混写。主要是字符串替换，将正常部分转化为pathinfo模式，然后再拼装
            $pathinfo = str_replace(array('&', '='), self::$urlSp, $pathinfo);
            $pathinfo = trim($pathinfo, '/');
            /*
            //支持pathinfo和普通模式的参数混写
            $queryEx = strstr($pathinfo, '&');
            $pathinfo = trim(str_replace($queryEx, '', $pathinfo), '/');
            $queryEx = str_replace(array('=','&'), '/', $queryEx);
            $pathinfo = $pathinfo.$queryEx;*/
            //用自定义分隔符分割，默认用/
            $pathinfoArr = explode(self::$urlSp, $pathinfo);
            $queryNew = array();
            //两两分割参数数组，然后遍历，来使得参数和值对应
            $paramsArr = array_chunk($pathinfoArr, 2);
            //得到处理后的GET参数
            foreach ($paramsArr as $key => $value) {
                if( count($value)==1 ) continue;
                !empty($value[0]) && $queryNew[$value[0]] = $value[1];
            }
            //更改queryNew参数数组：取得pathinfo模式下，前两个参数，变为queryNew参数中的控制器和方法
            $controller = key($queryNew);
            if( !empty($controller) ){
                $queryNew[__C__] = $controller;
                $method = current($queryNew);
                if(!empty($method)){
                    $queryNew[__M__] =  $method;
                }
                array_shift($queryNew);
            }
            return $queryNew;

        }

        /**
         * 解析url rewrite模式下的url，并获取参数
         * @access private
         * @return array $queryNew 返回参数数组
         */
        static private function parseRewriteUrl(){

            //解析url
            $params = parse_url(APP_URL);
            //如果解析当前url，获取的参数中，有query信息，则说明当前url使用了普通的url写法。直接返回$_GET即可。
            if( isset($params['query']) ){
                return $_GET;
            }
            /**
             * rewrite模式三者兼容
             * 设置queryString变量，用于保存伪静态、或path_info或正常模式的查询参数。伪静态模式可以三者兼容。
             */
            //1、rewrite模式下，兼容pathinfo
            $queryString = !empty($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : $_SERVER['PATH_INFO'];
            //2、rewrite模式下，兼容普通url
            $queryString = str_replace(array('&','='), self::$urlSp, $queryString);
            /**
             * 过滤url信息，获取正确的url查询参数
             */
            //1、去除查询参数中，左侧可能存在的/字符
            $pathinfo = ltrim($queryString, '/');
            //2、去除url查询参数中，右侧的后缀
            $suffixOffset = strrpos($pathinfo, self::$urlSuffix);
            //如果有url参数的后缀，则去除，否则不去除
            if( !empty($suffixOffset) ){
                $pathinfo = substr_replace($pathinfo, '', $suffixOffset);            
            }
            /*
            //处理url，使得可以支持pathinfo和普通url混写。主要是字符串替换，将正常部分转化为pathinfo模式，然后再拼装
            $queryEx = strstr($pathinfo, '&');
            $pathinfo = trim(str_replace($queryEx, '', $pathinfo), '/');
            $queryEx = str_replace(array('=','&'), '/', $queryEx);
            $pathinfo = $pathinfo.$queryEx;*/
            
            //用自定义url重写模式分隔符分割
            $pathinfoArr = explode(self::$urlSp, $pathinfo);
            $queryNew = array();
            //两两分割参数数组，然后遍历，来使得参数和值对应
            $paramsArr = array_chunk($pathinfoArr, 2);
            //将array_chunk之后的二维数组，变为一维数组
            foreach ($paramsArr as $key => $value) {
                if( count($value)==1 ) continue;
                !empty($value[0]) && $queryNew[$value[0]] = $value[1];
            }
            //获取参数数组
            P($queryNew);
            $controller = key($queryNew);
            //更改queryNew参数数组：取得rewrite下，前两个参数，变为queryNew参数中的控制器和方法
            if( !empty($controller) ){
                $queryNew[__C__] = $controller;
                $method = current($queryNew);
                if(!empty($method)){
                    $queryNew[__M__] =  $method;
                }
                array_shift($queryNew);
            }
            return $queryNew;

        }

        /**
         * 自动根据当前url配置，生成对应url
         * 例如：U('index/index', array('ss'=>'mm'))、U('index/index', 'mm=qq&cc=ss')
         * 注：使用字符串参数列表的形式，效率最高
         * @access public
         * @param  string         $path   定义了分组、控制器、方法的path字符串
         * @param  array||string  $params url参数数组
         * @return string         $url    生成适合当前url模式的url
         */
        static public function url($path, $params){

            //获取伪静态或path_info模式下的分隔符
            $separator = C('url_separator');
            $paths = explode('/', trim($path, '/'));
            //验证参数的合法性，不合法，直接返回false
            if( count($paths) < 2 ) return false;
            $mode = self::$urlMode;

            //如果url模式为普通模式
            if($mode==1){
                //处理没有用户自定义参数的情况
                $url = 'http://'.$_SERVER['HTTP_HOST'].$separator.APP_ROOT_FILE.'?';
                switch (count($paths)) {
                    //2代表非分组模式，下同
                    case 2:
                        //判断参数为数组还是字符串
                        if(is_array($params)){
                            $newPath = array();
                            foreach ($paths as $key => $value) {
                                $key = ($key===0) ? __C__ : __M__;
                                $newPath[$key] = $value;
                            }
                            $path = null;
                            $params = array_merge($newPath, $params);
                            foreach ($params as $key => $value) {
                                if($value=='index') continue;
                                $url .= $key.'='.$value.'&';
                            }
                            $url = rtrim($url, '&');
                        }elseif (is_string($params)) {
                            foreach ($paths as $key => $value) {
                                if($value=='index') continue;
                                $key = ($key===0) ? __C__ : __M__;
                                $url .= $key.'='.$value.'&';
                            }
                            $url = rtrim($url, '&').$params;
                        }
                        break;
                    case 3:
                        break;
                }
            //如果url模式为path_info模式
            }elseif($mode==2){
                $url = 'http://'.$_SERVER['HTTP_HOST'].$separator.APP_ROOT_FILE.$separator.str_replace('/', $separator, $path);
                if(empty($params)) return $url;
                switch (count($paths)) {
                    case 2:
                        if(is_array($params)){
                            foreach ($params as $key => $value) {
                                $url .= $separator.$key.$separator.$value;
                            }
                        }elseif (is_string($params)) {
                            $params = $separator.str_replace(array('=','&'), $separator, $params);
                            $url .= $params;
                        }
                        break;
                    case 3:
                        break;
                }
            //如果url模式为url重写模式
            }elseif($mode==3){
                //如果没有传递自定义url参数
                $url = 'http://'.$_SERVER['HTTP_HOST'].'/'.str_replace('/', $separator, $path);
                if(empty($params)) return $url.C('url_rewrite_suffix');
                switch (count($paths)) {
                    case 2:
                        if(is_array($params)){
                            foreach ($params as $key => $value) {
                                $url .= $separator.$key.$separator.$value;
                            }
                        }elseif (is_string($params)) {
                            $url .= $separator.str_replace(array('=','&'), $separator, $params);
                        }
                        $url .= C('url_rewrite_suffix');
                        break;
                    case 3:
                        break;
                }
            }
            return $url;

        }

        /**
         * 魔术引用的处理和处理gpc的特殊字符
         */
        static private function gpcSet(){

            //设置对魔术引用的处理和处理gpc的特殊字符
            if (!get_magic_quotes_gpc()) {
                $gpc = array(&$_POST,&$_GET,&$_COOKIE,&$_REQUEST);
                foreach ($gpc as $gpcKey=>&$value) {
                    if( !empty($value) ){
                        $value = array_map('addslashes', $value);
                    }
                }
            }

        }

    }